package midend.IR_Optimizer;

import config.Config;
import midend.IR_Instruction.*;
import midend.IR_Module;
import midend.IR_Value.IR_BaseBlock;
import midend.IR_Value.IR_FuncBlock;
import midend.IR_Value.IR_Value;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

public class Useless_Load_Eli implements IR_Opt{
    Map<IR_Value, LinkedList<IR_Value>> loadopt = new HashMap<>();
    Map<IR_Value,IR_Value> optmap = new HashMap<>();
    ArrayList<Load_Instruction> insertList = new ArrayList<>();
    IR_Module module;

    public Useless_Load_Eli(IR_Module module) {
        this.module = module;
    }

    public void run(){
        for(IR_FuncBlock funcBlock:module.getFuncBlocks()){
            Optimize(funcBlock);
        }
    }

    private void dealwithrename(IR_FuncBlock irFuncBlock) {
        for(IR_BaseBlock irBaseBlock:irFuncBlock.get_bb_List()) {
            for (Instruction instruction : irBaseBlock.get_Instruction_List()) {
                //条件跳转
                if (instruction instanceof Branch_Instruction branchInstruction && branchInstruction.br_type()) {
                    if (optmap.containsKey(branchInstruction.get_op_index(0))) {
                        branchInstruction.set_Op(0, optmap.get(branchInstruction.get_op_index(0)));
                    }
                } else if (instruction instanceof Calc_Instruction calcInstruction) {
                    if (optmap.containsKey(calcInstruction.get_op_index(1))) {
                        calcInstruction.set_Op(1, optmap.get(calcInstruction.get_op_index(1)));
                    }
                    if (optmap.containsKey(calcInstruction.get_op_index(2))) {
                        calcInstruction.set_Op(2, optmap.get(calcInstruction.get_op_index(2)));
                    }
                } else if (instruction instanceof Cond_Instruction condInstruction) {
                    if (optmap.containsKey(condInstruction.get_op_index(1))) {
                        condInstruction.set_Op(1, optmap.get(condInstruction.get_op_index(1)));
                    }
                    if (optmap.containsKey(condInstruction.get_op_index(2))) {
                        condInstruction.set_Op(2, optmap.get(condInstruction.get_op_index(2)));
                    }
                } else if (instruction instanceof Phi_Instruction phiInstruction) {
                    for (Map.Entry<IR_BaseBlock, IR_Value> entry : phiInstruction.get_ComingValues().entrySet()) {
                        if (optmap.containsKey(entry.getValue())) {
                            phiInstruction.set_ComingValue(optmap.get(entry.getValue()), entry.getKey());
                        }
                    }
                } else if (instruction instanceof Ret_Instruction retInstruction) {
                    if (retInstruction.get_op_index(0) != null && optmap.containsKey(retInstruction.get_op_index(0))) {
                        retInstruction.set_Op(0, optmap.get(retInstruction.get_op_index(0)));
                    }
                } else if (instruction instanceof Store_Instruction storeInstruction) {
                    if (optmap.containsKey(storeInstruction.get_op_index(1))) {
                        storeInstruction.set_Op(1, optmap.get(storeInstruction.get_op_index(1)));
                    }
                } else if (instruction instanceof Type_Trans_Instruction typeTransInstruction) {
                    if (optmap.containsKey(typeTransInstruction.origin())) {
                        typeTransInstruction.set_Origin(optmap.get(typeTransInstruction.origin()));
                    }
                } else if (instruction instanceof Zext_Instruction zextInstruction) {
                    if (optmap.containsKey(zextInstruction.get_op_index(1))) {
                        zextInstruction.set_Op(1, optmap.get(zextInstruction.get_op_index(1)));
                    }
                } else if (instruction instanceof Call_Instruction callInstruction) {
                    if (callInstruction.get_params().size() != 0) {
                        for (int i = 0; i < callInstruction.get_params().size(); i++) {
                            IR_Value para = callInstruction.get_params().get(i);
                            if (optmap.containsKey(para)) {
                                callInstruction.setParams(i, optmap.get(para));
                            }
                        }
                    }
                } else if (instruction instanceof Pointer_Instruction pointerInstruction) {
                    if (optmap.containsKey(pointerInstruction.get_base())) {
                        pointerInstruction.set_Op(1, optmap.get(pointerInstruction.get_base()));
                        pointerInstruction.set_Base(optmap.get(pointerInstruction.get_base()));
                    }
                    for (int i = 0; i < pointerInstruction.get_list().size(); i++) {
                        IR_Value para = pointerInstruction.get_list().get(i);
                        if (optmap.containsKey(para)) {
                            pointerInstruction.set_List(i, optmap.get(para));
                        }
                    }
                }
            }
        }
    }

    public void Optimize(IR_FuncBlock funcBlock) {
        ArrayList<Instruction> deletelist = new ArrayList<>();
        for(IR_BaseBlock irBaseBlock:funcBlock.get_bb_List()){
            loadopt.clear();
            optmap.clear();
            for(Instruction instruction:irBaseBlock.get_Instruction_List()){
                if(instruction instanceof Load_Instruction loadInstruction){
                    if(loadopt.containsKey(loadInstruction.getPointer())){
                        deletelist.add(loadInstruction);
                        optmap.put(loadInstruction.getRet(),loadopt.get(loadInstruction.getPointer()).getFirst());
                    }
                    else {
                        insertList.add(loadInstruction);
                        loadopt.put(loadInstruction.getPointer(),new LinkedList<>());
                        loadopt.get(loadInstruction.getPointer()).add(loadInstruction.getRet());
                    }
                }
                if(instruction instanceof Store_Instruction storeInstruction){
                    if(loadopt.containsKey(storeInstruction.getPointer())){
                        loadopt.remove(storeInstruction.getPointer());
                    }
                }
            }
            dealwithrename(funcBlock);
        }

        for(Instruction instruction:deletelist){
            instruction.remove();
        }
    }
}
